package moonshine
{
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	
	import mx.controls.scrollClasses.ScrollBar;
	import mx.core.UIComponent;
	import mx.events.ScrollEvent;
	
	public class TextEditor extends UIComponent
	{	
		internal var itemContainer:UIComponent;
		private var scrollBar:ScrollBar;
		
		private var lineHeight:int = 16;
		
		private var selectionManager:SelectionManager;
		private var editManager:EditManager;
		
		private var model:TextEditorModel;
		
		// Invalidation flags
		private var needsLayout:Boolean;
		private var needsScrollUpdate:Boolean;
		private var needsFullUpdate:Boolean;
		private var needsSelectionUpdate:Boolean;
		
		private var scrollPosition:int = 0;
		private var scrollDelta:int = 0;
		
		
		// Getters/Setters
		public function get dataProvider():String
		{ 
			return model.lines.join("\n"); 
		}
		public function set dataProvider(v:String):void 
		{
			// TODO: Handle this properly
			v = v.replace(new RegExp("\r\n", "g"), "\n");
			
			model.lines = v.split("\n");
			
			scrollBar.maxScrollPosition = model.lines.length-getRenderersNeeded();
			
			needsFullUpdate = true;
			invalidateDisplayList();
		}
		
		public function getRendererFromIndex(index:int):TextLineRenderer
		{
			if (index < 0 || index > model.lines.length) return null;
			if (index < scrollPosition || index > scrollPosition+model.itemRenderersInUse.length) return null;
			
			return model.itemRenderersInUse[index - scrollPosition];
		}
		
		public function invalidateLines():void
		{
			needsFullUpdate = true;
			needsSelectionUpdate = true;
			invalidateDisplayList();
		}
		
		public function invalidateSelection():void
		{
			needsSelectionUpdate = true;
			invalidateDisplayList();
		}
		
		public function TextEditor():void
		{
			model = new TextEditorModel();
			
			selectionManager = new SelectionManager();
			selectionManager.editor = this;
			selectionManager.model = model;
			
			editManager = new EditManager();
			editManager.editor = this;
			editManager.model = model;
			editManager.selectionManager = selectionManager;
		}
		
		private function getRenderersNeeded():int
		{
			return Math.ceil(height/lineHeight);
		}
		
		override protected function createChildren():void
		{
			super.createChildren();
			itemContainer = new UIComponent();
			addChild(itemContainer);
			
			itemContainer.addEventListener(MouseEvent.MOUSE_DOWN, selectionManager.handleMouseDown);
			itemContainer.addEventListener(MouseEvent.MOUSE_UP, selectionManager.handleMouseUp);
			
			scrollBar = new ScrollBar();
			scrollBar.minScrollPosition = 0;
			scrollBar.lineScrollSize = lineHeight;
			scrollBar.addEventListener(ScrollEvent.SCROLL, handleScroll);
			addChild(scrollBar);
		
			addEventListener(KeyboardEvent.KEY_DOWN, editManager.handleKeyDown);
		}
		
		
		
		private function handleScroll(event:ScrollEvent):void 
		{
			if (event.delta == 0) return;
			if (event.detail == 'thumbPosition') return;
			
			scrollDelta += event.delta;
			needsScrollUpdate = true;
			invalidateDisplayList();
		}
		
		
		private function getItemRenderers(howMany:int, beginningAtLine:int):Array 
		{
			var ret:Array = [];
			for (var i:int = 0; i < howMany; i++)
			{
				var rdr:TextLineRenderer;
				if (model.itemRenderersFree.length > 0)
				{
					rdr = model.itemRenderersFree.pop();
				}
				else
				{ 
					rdr = new TextLineRenderer();
					rdr.addEventListener(KeyboardEvent.KEY_DOWN, editManager.handleKeyDown);
					itemContainer.addChild(rdr);
				}
				
				rdr.data = model.lines[beginningAtLine+i];
				rdr.dataIndex = beginningAtLine+i;
				ret.push(rdr);
			}
			
			return ret;
		}
		
		private function freeItemRenderers(startIndex:int, howMany:int):void 
		{
			var toRemove:Array = model.itemRenderersInUse.splice(startIndex, howMany);
			for each (var rdr:TextLineRenderer in toRemove) 
			{
				rdr.x = -2000;
				rdr.y = -2000;
			}
			
			model.itemRenderersFree = model.itemRenderersFree.concat(toRemove);
		}
		
		private function freeRenderersAtTop(howMany:int):void 
		{
			freeItemRenderers(0, howMany);
		}
		
		private function freeRenderersAtBottom(howMany:int):void
		{
			freeItemRenderers(model.itemRenderersInUse.length-howMany, howMany);
		}
		
		private function clearAllRenderers():void 
		{
			freeItemRenderers(0, model.itemRenderersInUse.length);
		}
		
		private function updateDataProvider():void
		{
			clearAllRenderers();
			var renderersNeeded:int = Math.min(getRenderersNeeded(), model.lines.length);
			model.itemRenderersInUse = getItemRenderers(renderersNeeded, scrollPosition);
			
			scrollBar.maxScrollPosition = model.lines.length-getRenderersNeeded();
			
			needsLayout = true;
		}
		
		private function updateScroll():void
		{
			if (scrollDelta > getRenderersNeeded()) 
			{
				needsFullUpdate = true;
				scrollPosition += scrollDelta;
				scrollDelta = 0;
				return;
			} 
			else if (-scrollDelta > getRenderersNeeded())
			{
				needsFullUpdate = true;
				scrollPosition -= -scrollDelta;
				scrollDelta = 0;
				return;
			}
			
			var linesRemaining:int;
			var affectedLines:int;
			var newRenderers:Array;
			
			if (scrollDelta > 0) // Scroll down
			{
				linesRemaining = model.lines.length-(scrollPosition+model.itemRenderersInUse.length);
				affectedLines = Math.min(scrollDelta, linesRemaining);
																						
				newRenderers = getItemRenderers(affectedLines, scrollPosition+model.itemRenderersInUse.length);
				freeRenderersAtTop(affectedLines);
				model.itemRenderersInUse = model.itemRenderersInUse.concat(newRenderers);
				
				scrollPosition += scrollDelta;
			}
			else // Scroll up
			{
				linesRemaining = scrollPosition;
				affectedLines = Math.min(-scrollDelta, linesRemaining);
				
				freeRenderersAtBottom(affectedLines);
				newRenderers = getItemRenderers(affectedLines, scrollPosition-affectedLines);
				model.itemRenderersInUse = newRenderers.concat(model.itemRenderersInUse);
				
				scrollPosition -= -scrollDelta;
			}
			
			scrollDelta = 0;
			needsLayout = true;
			needsSelectionUpdate = true;
		}
		
		private function updateLayout():void
		{
			var yStart:int = 0;
			var rdr:TextLineRenderer;
			for (var i:int = 0; i < model.itemRenderersInUse.length; i++)
			{
				rdr = model.itemRenderersInUse[i];
				rdr.y = yStart;
				rdr.x = 0;				
				yStart += lineHeight;
				
				if (i+scrollPosition == model.selectedLineIndex)
				{
					rdr.focus = true;
					rdr.drawCaret(model.caretIndex);
				}
			}
			
		}
		
		private function updateSelection():void
		{
			var rdr:TextLineRenderer;

			for (var i:int = 0; i < model.itemRenderersInUse.length; i++)
			{
				rdr = model.itemRenderersInUse[i];
				if (i+scrollPosition == model.selectedLineIndex)
				{
					rdr.focus = true;
					rdr.caretPosition = model.caretIndex;
					rdr.drawSelection(model.selectionStartCharIndex, model.caretIndex);
				}
				else
				{
					rdr.focus = false;
					rdr.removeSelection();
				}
			}
		}
		
		private function updateMultilineSelection():void
		{
			var rdr:TextLineRenderer;
			
			for (var i:int = 0; i < model.itemRenderersInUse.length; i++)
			{
				rdr = model.itemRenderersInUse[i];
				if (i+scrollPosition == model.selectionStartLineIndex) 
				{
					if (model.selectionStartLineIndex > model.selectedLineIndex)
					{
						rdr.drawSelection(0, model.selectionStartCharIndex);
					}
					else
					{
						rdr.drawFullLineSelection(width, model.selectionStartCharIndex);
					}
					rdr.focus = false;
				} 
				else if (i+scrollPosition == model.selectedLineIndex)
				{
					if (model.selectedLineIndex > model.selectionStartLineIndex)
					{
						rdr.drawSelection(0, model.caretIndex);
					}
					else
					{
						rdr.drawFullLineSelection(width, model.caretIndex); 
					}
					rdr.caretPosition = model.caretIndex;
					rdr.focus = true;
				}
				else if (model.selectionStartLineIndex < i+scrollPosition 
						 && model.selectedLineIndex > i+scrollPosition)
				{
					rdr.drawFullLineSelection(width);
					rdr.focus = false;				  
				}
				else if (model.selectionStartLineIndex > i+scrollPosition
						&& model.selectedLineIndex < i+scrollPosition)
				{
					rdr.drawFullLineSelection(width);
					rdr.focus = false;
				}
				else
				{
					rdr.focus = false;
					rdr.removeSelection();
				}
			}
		}
		
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
		{
			super.updateDisplayList(unscaledWidth, unscaledHeight);
			
			scrollBar.x = unscaledWidth-scrollBar.width;
			scrollBar.height = unscaledHeight;
			
			scrollBar.pageSize = getRenderersNeeded();
			
			
			if (needsScrollUpdate)
			{
				updateScroll();
				needsScrollUpdate = false;	
			}
			if (needsFullUpdate) 
			{
				updateDataProvider();
				needsFullUpdate = false;
			}
			if (needsSelectionUpdate)
			{
				if (model.hasMultilineSelection)
				{
					updateMultilineSelection();
				}
				else
				{
					updateSelection();
				} 
				needsSelectionUpdate = false;
			}
			if (needsLayout)
			{
				updateLayout();
				needsLayout = false;
			}
						
		}	
		
		
	}
}